package com.wujiale.javacore.xml;

import org.w3c.dom.*;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;

public class JSONConverter {
    public static void main(String[] args) throws IOException, ParserConfigurationException, SAXException {
        // get xml file by path
        File file = new File("");
        String canonicalPath = file.getCanonicalPath();
        String path = canonicalPath + "/src/main/resources/xml/pom.xml";
        File xmlFile = new File(path);
        // create xml parse builder
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
        Document doc = documentBuilder.parse(xmlFile);
        Element root = doc.getDocumentElement();
        System.out.println(convert(root, 0));
    }

    public static StringBuilder convert(Node node, int level){
        if (node instanceof Element){
            return elementObject((Element) node, level);
        } else if (node instanceof CharacterData) {
            return characterString((CharacterData) node, level);
        }else {
            return pad(new StringBuilder(), level).append(
                    jsonEscape(node.getClass().getName())
            );
        }
    }

    private static StringBuilder elementObject(Element elem, int level){
        var result = new StringBuilder();
        pad(result, level).append("{\n");
        pad(result, level+1).append("\"name\":");
        result.append(jsonEscape(elem.getTagName()));
        NamedNodeMap attrs = elem.getAttributes();
        if (attrs.getLength() > 0){
            pad(result.append(",\n"), level+1).append("\"attributes\":");
            result.append(attributeObject(attrs));
        }
        NodeList childNodes = elem.getChildNodes();
        if (childNodes.getLength() > 0){
            pad(result.append(",\n"), level+1).append("\"children\": [\n");
            for (int i = 0; i < childNodes.getLength(); i++) {
                if (i > 0) result.append(",\n");
                result.append(convert(childNodes.item(i), level+2));
            }
            result.append("\n");
            pad(result, level+1).append("]\n");
        }
        pad(result, level).append("}");
        return result;
    }

    private static StringBuilder pad(StringBuilder builder, int level){
        for (int i = 0; i < level; i++) {
            builder.append(" ");
        }
        return builder;
    }

    private static StringBuilder jsonEscape(String str){
        var result = new StringBuilder("\"");
        for (int i = 0; i < str.length(); i++) {
            char ch = str.charAt(i);
            String replacement = replacements.get(ch);
            if (replacement == null) result.append(ch);
            else result.append(replacement);
        }
        result.append("\"");
        return result;
    }

    private static Map<Character, String> replacements = Map.of('\b', "\\b", '\f', "\\f", '\n',
            "\\n",'\r', "\\r",'\t', "\\t", '"', "\\\"", '\\', "\\\\");

    private static StringBuilder attributeObject(NamedNodeMap attrs){
        var result = new StringBuilder("{");
        for (int i = 0; i < attrs.getLength(); i++) {
            if (i > 0) result.append(", ");
            result.append(jsonEscape(attrs.item(i).getNodeName()));
            result.append(": ");
            result.append(jsonEscape(attrs.item(i).getNodeValue()));
        }
        result.append("}");
        return result;
    }

    private static StringBuilder characterString(CharacterData node, int level){
        var result = new StringBuilder();
        StringBuilder data = jsonEscape(node.getData());
        if (node instanceof Comment) data.insert(1, "Comment: ");
        pad(result, level).append(data);
        return result;
    }
}
